/* Copyright (c) 2020 XEPIC Corporation Limited */
/*
 * Copyright (c) 2002-2020 Stephen Williams (steve@icarus.com)
 *
 *    This source code is free software; you can redistribute it
 *    and/or modify it in source code form under the terms of the GNU
 *    General Public License as published by the Free Software
 *    Foundation; either version 2 of the License, or (at your option)
 *    any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 * USA.
 */

#include "compiler.h"
#include "config.h"
#include "functor.h"
#include "ivl_assert.h"
#include "netlist.h"
#include "netmisc.h"
#include "netvector.h"

using namespace std;

/* General notes on enables and bitmasks.
 *
 * When synthesising an asynchronous process that contains conditional
 * statements (if/case statements), we need to determine the conditions
 * that cause each nexus driven by that process to be updated. If a
 * nexus is not updated under all circumstances, we must infer a latch.
 * To this end, we generate an enable signal for each output nexus. As
 * we walk the statement tree for the process, for each substatement we
 * pass the enable signals generated so far into the synth_async method,
 * and on return from the synth_async method, the enable signals will be
 * updated to reflect any conditions introduced by that substatement.
 * Once we have synthesised all the statements for that process, if an
 * enable signal is not tied high, we must infer a latch for that nexus.
 *
 * When synthesising a synchronous process, we use the synth_async method
 * to synthesise the combinatorial inputs to the D pins of the flip-flops
 * we infer for that process. In this case the enable signal can be used
 * as a clock enable for the flip-flop. This saves us explicitly feeding
 * back the flip-flop output to undriven inputs of any synthesised muxes.
 *
 * The strategy described above is not sufficient when not all bits in
 * a nexus are treated identically (i.e. different conditional clauses
 * drive differing parts of the same vector). To handle this properly,
 * we would (potentially) need to generate a separate enable signal for
 * each bit in the vector. This would be a lot of work, particularly if
 * we wanted to eliminate duplicates. For now, the strategy employed is
 * to maintain a bitmask for each output nexus that identifies which bits
 * in the nexus are unconditionally driven (driven by every clause). When
 * we finish synthesising an asynchronous process, if the bitmask is not
 * all ones, we must infer a latch. This currently results in an error,
 * because to safely synthesise such a latch we would need the bit-level
 * gate enables. When we finish synthesising a synchronous process, if
 * the bitmask is not all ones, we explicitly feed the flip-flop outputs
 * back to undriven inputs of any synthesised muxes to ensure undriven
 * parts of the vector retain their previous state when the flip-flop is
 * clocked.
 *
 * The enable signals are passed as links to the current output nexus
 * for each signal. If an enable signal is not linked, this is treated
 * as if the signal was tied low.
 *
 * The bitmasks are passed as bool vectors. 'true' indicates a bit is
 * unconditionally driven. An empty vector (size = 0) indicates that
 * the current substatement doesn't drive any bits in the nexus.
 */

static void qualify_enable(Design* des, NetScope* scope, NetNet* qualifier,
                           bool active_state, NetLogic::TYPE gate_type,
                           Link& enable_i, Link& enable_o) {
  if (enable_i.is_linked(scope->tie_lo())) {
    connect(enable_o, scope->tie_lo());
    return;
  }

  if (active_state == false) {
    NetLogic* gate =
        new NetLogic(scope, scope->local_symbol(), 2, NetLogic::NOT, 1);
    des->add_node(gate);
    connect(gate->pin(1), qualifier->pin(0));

    NetNet* sig = new NetNet(scope, scope->local_symbol(), NetNet::WIRE,
                             &netvector_t::scalar_logic);
    sig->local_flag(true);
    connect(sig->pin(0), gate->pin(0));

    qualifier = sig;
  }

  if (enable_i.is_linked(scope->tie_hi())) {
    connect(enable_o, qualifier->pin(0));
    return;
  }

  NetLogic* gate = new NetLogic(scope, scope->local_symbol(), 3, gate_type, 1);
  des->add_node(gate);
  connect(gate->pin(1), qualifier->pin(0));
  connect(gate->pin(2), enable_i);
  connect(enable_o, gate->pin(0));

  NetNet* sig = new NetNet(scope, scope->local_symbol(), NetNet::WIRE,
                           &netvector_t::scalar_logic);
  sig->local_flag(true);
  connect(sig->pin(0), gate->pin(0));
}

static void multiplex_enables(Design* des, NetScope* scope, NetNet* select,
                              Link& enable_1, Link& enable_0, Link& enable_o) {
  if (!enable_1.is_linked() && !enable_0.is_linked()) return;

  if (enable_1.is_linked(scope->tie_hi()) &&
      enable_0.is_linked(scope->tie_hi())) {
    connect(enable_o, scope->tie_hi());
    return;
  }

  if (enable_1.is_linked(scope->tie_lo()) || !enable_1.is_linked()) {
    qualify_enable(des, scope, select, false, NetLogic::AND, enable_0,
                   enable_o);
    return;
  }
  if (enable_0.is_linked(scope->tie_lo()) || !enable_0.is_linked()) {
    qualify_enable(des, scope, select, true, NetLogic::AND, enable_1, enable_o);
    return;
  }
  if (enable_1.is_linked(scope->tie_hi())) {
    qualify_enable(des, scope, select, true, NetLogic::OR, enable_0, enable_o);
    return;
  }
  if (enable_0.is_linked(scope->tie_hi())) {
    qualify_enable(des, scope, select, false, NetLogic::OR, enable_1, enable_o);
    return;
  }

  NetMux* mux = new NetMux(scope, scope->local_symbol(), 1, 2, 1);
  des->add_node(mux);
  connect(mux->pin_Sel(), select->pin(0));
  connect(mux->pin_Data(1), enable_1);
  connect(mux->pin_Data(0), enable_0);
  connect(enable_o, mux->pin_Result());

  NetNet* sig = new NetNet(scope, scope->local_symbol(), NetNet::WIRE,
                           &netvector_t::scalar_logic);
  sig->local_flag(true);
  connect(sig->pin(0), mux->pin_Result());
}

static void merge_sequential_enables(Design* des, NetScope* scope,
                                     Link& top_enable, Link& sub_enable) {
  if (!sub_enable.is_linked()) return;

  if (top_enable.is_linked(scope->tie_hi())) return;

  if (sub_enable.is_linked(scope->tie_hi())) top_enable.unlink();

  if (top_enable.is_linked()) {
    NetLogic* gate =
        new NetLogic(scope, scope->local_symbol(), 3, NetLogic::OR, 1);
    des->add_node(gate);
    connect(gate->pin(1), sub_enable);
    connect(gate->pin(2), top_enable);
    top_enable.unlink();
    connect(top_enable, gate->pin(0));

    NetNet* sig = new NetNet(scope, scope->local_symbol(), NetNet::WIRE,
                             &netvector_t::scalar_logic);
    sig->local_flag(true);
    connect(sig->pin(0), gate->pin(0));
  } else {
    connect(top_enable, sub_enable);
  }
}

static void merge_sequential_masks(NetProc::mask_t& top_mask,
                                   NetProc::mask_t& sub_mask) {
  if (sub_mask.size() == 0) return;

  if (top_mask.size() == 0) {
    top_mask = sub_mask;
    return;
  }

  assert(top_mask.size() == sub_mask.size());
  for (unsigned idx = 0; idx < top_mask.size(); idx += 1) {
    if (sub_mask[idx] == true) top_mask[idx] = true;
  }
}

static void merge_parallel_masks(NetProc::mask_t& top_mask,
                                 NetProc::mask_t& sub_mask) {
  if (sub_mask.size() == 0) return;

  if (top_mask.size() == 0) {
    top_mask = sub_mask;
    return;
  }

  assert(top_mask.size() == sub_mask.size());
  for (unsigned idx = 0; idx < top_mask.size(); idx += 1) {
    if (sub_mask[idx] == false) top_mask[idx] = false;
  }
}

static bool all_bits_driven(NetProc::mask_t& mask) {
  if (mask.size() == 0) return false;

  for (unsigned idx = 0; idx < mask.size(); idx += 1) {
    if (mask[idx] == false) return false;
  }
  return true;
}

bool NetProcTop::tie_off_floating_inputs_(Design* des, NexusSet& nex_map,
                                          NetBus& nex_in,
                                          vector<NetProc::mask_t>& bitmasks,
                                          bool is_ff_input) {
  bool flag = true;
  for (unsigned idx = 0; idx < nex_in.pin_count(); idx += 1) {
    if (nex_in.pin(idx).nexus()->has_floating_input()) {
      if (all_bits_driven(bitmasks[idx])) {
        // If all bits are unconditionally driven, we can
        // use the enable signal to prevent the flip-flop/
        // latch from updating when an undriven mux input
        // is selected, so we can just tie off the input.
        unsigned width = nex_map[idx].wid;
        NetLogic* gate = new NetLogic(scope(), scope()->local_symbol(), 1,
                                      NetLogic::PULLDOWN, width);
        des->add_node(gate);
        connect(nex_in.pin(idx), gate->pin(0));

        if (nex_in.pin(idx).nexus()->pick_any_net()) continue;

        ivl_variable_type_t data_type = IVL_VT_LOGIC;
        netvector_t* tmp_vec = new netvector_t(data_type, width - 1, 0);
        NetNet* sig =
            new NetNet(scope(), scope()->local_symbol(), NetNet::WIRE, tmp_vec);
        sig->local_flag(true);
        connect(sig->pin(0), gate->pin(0));
      } else if (is_ff_input) {
        // For a flip-flop, we can feed back the output
        // to ensure undriven bits hold their last value.
        connect(nex_in.pin(idx), nex_map[idx].lnk);
      } else {
        // This infers a latch, but without generating
        // gate enable signals at the bit-level, we
        // can't safely latch the undriven bits (we
        // shouldn't generate combinatorial loops).
        cerr << get_fileline() << ": warning: A latch "
             << "has been inferred for some bits of '"
             << nex_map[idx].lnk.nexus()->pick_any_net()->name() << "'."
             << endl;

        cerr << get_fileline()
             << ": sorry: Bit-level "
                "latch gate enables are not currently "
                "supported in synthesis."
             << endl;
        des->errors += 1;
        flag = false;
      }
    }
  }
  return flag;
}

bool NetProc::synth_async(Design*, NetScope*, NexusSet&, NetBus&, NetBus&,
                          vector<mask_t>&) {
  return false;
}

/*
 * Async synthesis of assignments is done by synthesizing the rvalue
 * expression, then connecting the l-value directly to the output of
 * the r-value.
 *
 * The nex_map is the O-set for the statement, and lists the positions
 * of the outputs as the caller wants results linked up. The nex_out,
 * however, is the set of nexa that are to actually get linked to the
 * r-value.
 */
bool NetAssignBase::synth_async(Design* des, NetScope* scope, NexusSet& nex_map,
                                NetBus& nex_out, NetBus& enables,
                                vector<mask_t>& bitmasks) {
  if (dynamic_cast<NetCAssign*>(this) || dynamic_cast<NetDeassign*>(this) ||
      dynamic_cast<NetForce*>(this) || dynamic_cast<NetRelease*>(this)) {
    cerr << get_fileline()
         << ": sorry: Procedural continuous "
            "assignment is not currently supported in synthesis."
         << endl;
    des->errors += 1;
    return false;
  }

  /* If the lval is a concatenation, synthesise each part
     separately. */
  if (lval_->more) {
    /* Temporarily set the lval_ and rval_ fields for each
       part in turn and recurse. Restore them when done. */
    NetAssign_* full_lval = lval_;
    NetExpr* full_rval = rval_;
    unsigned offset = 0;
    bool flag = true;
    while (lval_) {
      unsigned width = lval_->lwidth();
      NetEConst* base = new NetEConst(verinum(offset));
      base->set_line(*this);
      rval_ = new NetESelect(full_rval->dup_expr(), base, width);
      rval_->set_line(*this);
      eval_expr(rval_, width);
      NetAssign_* more = lval_->more;
      lval_->more = 0;
      if (!synth_async(des, scope, nex_map, nex_out, enables, bitmasks))
        flag = false;
      lval_ = lval_->more = more;
      offset += width;
    }
    lval_ = full_lval;
    rval_ = full_rval;
    return flag;
  }

  assert(rval_);
  NetNet* rsig = rval_->synthesize(des, scope, rval_);
  assert(rsig);

  if (lval_->word() && !dynamic_cast<NetEConst*>(lval_->word())) {
    cerr << get_fileline()
         << ": sorry: Assignment to variable "
            "location in memory is not currently supported in "
            "synthesis."
         << endl;
    des->errors += 1;
    return false;
  }

  NetNet* lsig = lval_->sig();
  if (!lsig) {
    cerr << get_fileline()
         << ": error: "
            "NetAssignBase::synth_async on unsupported lval ";
    dump_lval(cerr);
    cerr << endl;
    des->errors += 1;
    return false;
  }

  if (debug_synth2) {
    cerr << get_fileline() << ": NetAssignBase::synth_async: "
         << "l-value signal is " << lsig->vector_width() << " bits, "
         << "r-value signal is " << rsig->vector_width() << " bits." << endl;
    cerr << get_fileline() << ": NetAssignBase::synth_async: "
         << "lval_->lwidth()=" << lval_->lwidth() << endl;
    cerr << get_fileline() << ": NetAssignBase::synth_async: "
         << "lsig = " << scope_path(scope) << "." << lsig->name() << endl;
    if (const NetExpr* base = lval_->get_base()) {
      cerr << get_fileline() << ": NetAssignBase::synth_async: "
           << "base_=" << *base << endl;
    }
    cerr << get_fileline() << ": NetAssignBase::synth_async: "
         << "nex_map.size()==" << nex_map.size()
         << ", nex_out.pin_count()==" << nex_out.pin_count() << endl;
  }

  unsigned ptr = 0;
  if (nex_out.pin_count() > 1) {
    NexusSet tmp_set;
    nex_output(tmp_set);
    ivl_assert(*this, tmp_set.size() == 1);
    ptr = nex_map.find_nexus(tmp_set[0]);
    ivl_assert(*this, nex_out.pin_count() > ptr);
    ivl_assert(*this, enables.pin_count() > ptr);
    ivl_assert(*this, bitmasks.size() > ptr);
  } else {
    ivl_assert(*this, nex_out.pin_count() == 1);
    ivl_assert(*this, enables.pin_count() == 1);
    ivl_assert(*this, bitmasks.size() == 1);
  }

  unsigned lval_width = lval_->lwidth();
  unsigned lsig_width = lsig->vector_width();
  ivl_assert(*this, nex_map[ptr].wid == lsig_width);

  // Here we note if the l-value is actually a bit/part
  // select. If so, generate a NetPartSelect to perform the select.
  bool is_part_select = lval_width != lsig_width;

  long base_off = 0;
  if (is_part_select && !scope->loop_index_tmp.empty()) {
    // If we are within a NetForLoop, there may be an index
    // value. That is collected from the scope member
    // loop_index_tmp, and the evaluate_function method
    // knows how to apply it.
    ivl_assert(*this, !scope->loop_index_tmp.empty());
    ivl_assert(*this, lval_width < lsig_width);

    // Evaluate the index expression to a constant.
    const NetExpr* base_expr_raw = lval_->get_base();
    ivl_assert(*this, base_expr_raw);
    NetExpr* base_expr =
        base_expr_raw->evaluate_function(*this, scope->loop_index_tmp);
    if (!eval_as_long(base_off, base_expr)) {
      ivl_assert(*this, 0);
    }
    ivl_assert(*this, base_off >= 0);

    ivl_variable_type_t tmp_data_type = rsig->data_type();
    netvector_t* tmp_type = new netvector_t(tmp_data_type, lsig_width - 1, 0);

    NetNet* tmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE,
                             NetNet::not_an_array, tmp_type);
    tmp->local_flag(true);
    tmp->set_line(*this);

    NetPartSelect* ps =
        new NetPartSelect(tmp, base_off, lval_width, NetPartSelect::PV);
    ps->set_line(*this);
    des->add_node(ps);

    connect(ps->pin(0), rsig->pin(0));
    rsig = tmp;

  } else if (is_part_select) {
    // In this case, there is no loop_index_tmp, so we are
    // not within a NetForLoop. Generate a NetSubstitute
    // object to handle the bit/part-select in the l-value.
    ivl_assert(*this, scope->loop_index_tmp.empty());
    ivl_assert(*this, lval_width < lsig_width);

    const NetExpr* base_expr_raw = lval_->get_base();
    ivl_assert(*this, base_expr_raw);
    NetExpr* base_expr =
        base_expr_raw->evaluate_function(*this, scope->loop_index_tmp);
    if (!eval_as_long(base_off, base_expr)) {
      cerr << get_fileline()
           << ": sorry: assignment to variable "
              "bit location is not currently supported in "
              "synthesis."
           << endl;
      des->errors += 1;
      return false;
    }
    ivl_assert(*this, base_off >= 0);

    ivl_variable_type_t tmp_data_type = rsig->data_type();
    netvector_t* tmp_type = new netvector_t(tmp_data_type, lsig_width - 1, 0);

    NetNet* tmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE,
                             NetNet::not_an_array, tmp_type);
    tmp->local_flag(true);
    tmp->set_line(*this);

    NetNet* isig = nex_out.pin(ptr).nexus()->pick_any_net();
    if (isig) {
      if (debug_synth2) {
        cerr << get_fileline() << ": NetAssignBase::synth_async: "
             << " Found an isig:" << endl;
        nex_out.pin(ptr).dump_link(cerr, 8);
      }
    } else {
      if (debug_synth2) {
        cerr << get_fileline() << ": NetAssignBase::synth_async: "
             << " Found no isig, resorting to lsig." << endl;
      }
      isig = new NetNet(scope, scope->local_symbol(), NetNet::WIRE,
                        NetNet::not_an_array, tmp_type);
      isig->local_flag(true);
      isig->set_line(*this);
      connect(isig->pin(0), nex_out.pin(ptr));
    }
    ivl_assert(*this, isig);
    NetSubstitute* ps = new NetSubstitute(isig, rsig, lsig_width, base_off);
    ps->set_line(*this);
    des->add_node(ps);

    connect(ps->pin(0), tmp->pin(0));
    rsig = tmp;
  }

  rsig = crop_to_width(des, rsig, lsig_width);

  ivl_assert(*this, rsig->pin_count() == 1);
  nex_out.pin(ptr).unlink();
  enables.pin(ptr).unlink();
  connect(nex_out.pin(ptr), rsig->pin(0));
  connect(enables.pin(ptr), scope->tie_hi());

  mask_t& bitmask = bitmasks[ptr];
  if (is_part_select) {
    if (bitmask.size() == 0) {
      bitmask = mask_t(lsig_width, false);
    }
    ivl_assert(*this, bitmask.size() == lsig_width);
    for (unsigned idx = 0; idx < lval_width; idx += 1) {
      bitmask[base_off + idx] = true;
    }
  } else if (bitmask.size() > 0) {
    for (unsigned idx = 0; idx < bitmask.size(); idx += 1) {
      bitmask[idx] = true;
    }
  } else {
    bitmask = mask_t(lsig_width, true);
  }

  /* This lval_ represents a reg that is a WIRE in the
     synthesized results. This function signals the destructor
     to change the REG that this l-value refers to into a
     WIRE. It is done then, at the last minute, so that pending
     synthesis can continue to work with it as a REG. */
  lval_->turn_sig_to_wire_on_release();

  return true;
}

bool NetProc::synth_async_block_substatement_(Design* des, NetScope* scope,
                                              NexusSet& nex_map,
                                              NetBus& nex_out, NetBus& enables,
                                              vector<mask_t>& bitmasks,
                                              NetProc* substmt) {
  ivl_assert(*this, nex_map.size() == nex_out.pin_count());
  ivl_assert(*this, nex_map.size() == enables.pin_count());
  ivl_assert(*this, nex_map.size() == bitmasks.size());

  // Create a temporary map of the output only from this statement.
  NexusSet tmp_map;
  substmt->nex_output(tmp_map);
  if (debug_synth2) {
    cerr << get_fileline() << ": NetProc::synth_async_block_substatement_: "
         << "tmp_map.size()==" << tmp_map.size() << " for statement at "
         << substmt->get_fileline() << endl;
    for (unsigned idx = 0; idx < nex_out.pin_count(); idx += 1) {
      cerr << get_fileline() << ": NetProc::synth_async_block_substatement_: "
           << "incoming nex_out[" << idx << "] dump link" << endl;
      nex_out.pin(idx).dump_link(cerr, 8);
    }
  }

  // Create temporary variables to collect the output from the synthesis.
  NetBus tmp_out(scope, tmp_map.size());
  NetBus tmp_ena(scope, tmp_map.size());
  vector<mask_t> tmp_masks(tmp_map.size());

  // Map (and move) the accumulated nex_out for this block
  // to the version that we can pass to the next statement.
  // We will move the result back later.
  for (unsigned idx = 0; idx < tmp_out.pin_count(); idx += 1) {
    unsigned ptr = nex_map.find_nexus(tmp_map[idx]);
    ivl_assert(*this, ptr < nex_out.pin_count());
    connect(tmp_out.pin(idx), nex_out.pin(ptr));
    nex_out.pin(ptr).unlink();
  }

  if (debug_synth2) {
    for (unsigned idx = 0; idx < nex_map.size(); idx += 1) {
      cerr << get_fileline()
           << ": NetProc::synth_async_block_substatement_: nex_map[" << idx
           << "] dump link, base=" << nex_map[idx].base
           << ", wid=" << nex_map[idx].wid << endl;
      nex_map[idx].lnk.dump_link(cerr, 8);
    }
    for (unsigned idx = 0; idx < tmp_map.size(); idx += 1) {
      cerr << get_fileline()
           << ": NetProc::synth_async_block_substatement_: tmp_map[" << idx
           << "] dump link, base=" << tmp_map[idx].base
           << ", wid=" << tmp_map[idx].wid << endl;
      tmp_map[idx].lnk.dump_link(cerr, 8);
    }
    for (unsigned idx = 0; idx < tmp_out.pin_count(); idx += 1) {
      cerr << get_fileline()
           << ": NetProc::synth_async_block_substatement_: tmp_out[" << idx
           << "] dump link" << endl;
      tmp_out.pin(idx).dump_link(cerr, 8);
    }
  }

  bool flag =
      substmt->synth_async(des, scope, tmp_map, tmp_out, tmp_ena, tmp_masks);

  if (debug_synth2) {
    cerr << get_fileline()
         << ": NetProc::synth_async_block_substatement_: "
            "substmt->synch_async(...) --> "
         << (flag ? "true" : "false") << " for statement at "
         << substmt->get_fileline() << "." << endl;
  }

  if (!flag) return false;

  // Now map the output from the substatement back to the
  // outputs for this block.
  for (unsigned idx = 0; idx < tmp_out.pin_count(); idx += 1) {
    unsigned ptr = nex_map.find_nexus(tmp_map[idx]);
    ivl_assert(*this, ptr < nex_out.pin_count());
    if (debug_synth2) {
      cerr << get_fileline() << ": NetProc::synth_async_block_substatement_: "
           << "tmp_out.pin(" << idx << "):" << endl;
      tmp_out.pin(idx).dump_link(cerr, 8);
    }
    connect(nex_out.pin(ptr), tmp_out.pin(idx));

    merge_sequential_enables(des, scope, enables.pin(ptr), tmp_ena.pin(idx));

    merge_sequential_masks(bitmasks[ptr], tmp_masks[idx]);
  }

  return true;
}

/*
 * Sequential blocks are translated to asynchronous logic by
 * translating each statement of the block, in order, into gates.
 * The nex_out for the block is the union of the nex_out for all
 * the substatements.
 */
bool NetBlock::synth_async(Design* des, NetScope* scope, NexusSet& nex_map,
                           NetBus& nex_out, NetBus& enables,
                           vector<mask_t>& bitmasks) {
  if (last_ == 0) {
    return true;
  }

  bool flag = true;
  NetProc* cur = last_;
  do {
    cur = cur->next_;

    bool sub_flag = synth_async_block_substatement_(
        des, scope, nex_map, nex_out, enables, bitmasks, cur);
    flag = flag && sub_flag;

  } while (cur != last_);

  return flag;
}

/*
 * This function is used to fix up a MUX selector to be no longer than
 * it needs to be. The general idea is that if the selector needs to
 * be only N bits, but is actually M bits, we translate it to this:
 *
 *     osig = { |esig[M-1:N-1], esig[N-2:0] }
 *
 * This obviously implies that (N >= 2) and (M >= N). In the code
 * below, N is sel_need, and M is sel_got (= esig->vector_width()).
 */
static NetNet* mux_selector_reduce_width(Design* des, NetScope* scope,
                                         const LineInfo& loc, NetNet* esig,
                                         unsigned sel_need) {
  const unsigned sel_got = esig->vector_width();

  ivl_assert(*esig, sel_got >= sel_need);

  // If the actual width matches the desired width (M==N) then
  // osig is esig itself. We're done.
  if (sel_got == sel_need) return esig;

  if (debug_synth2) {
    cerr << loc.get_fileline() << ": mux_selector_reduce_width: "
         << "Reduce selector width=" << sel_got << " to " << sel_need
         << " bits." << endl;
  }

  ivl_assert(*esig, sel_need >= 2);

  // This is the output signal, osig.
  ivl_variable_type_t osig_data_type = IVL_VT_LOGIC;
  netvector_t* osig_vec = new netvector_t(osig_data_type, sel_need - 1, 0);
  NetNet* osig =
      new NetNet(scope, scope->local_symbol(), NetNet::TRI, osig_vec);
  osig->local_flag(true);
  osig->set_line(loc);

  // Create the concat: osig = {...,...}
  NetConcat* osig_cat = new NetConcat(scope, scope->local_symbol(), sel_need, 2,
                                      !disable_concatz_generation);
  osig_cat->set_line(loc);
  des->add_node(osig_cat);
  connect(osig_cat->pin(0), osig->pin(0));

  // Create the part select esig[N-2:0]...
  NetPartSelect* ps0 =
      new NetPartSelect(esig, 0, sel_need - 1, NetPartSelect::VP);
  ps0->set_line(loc);
  des->add_node(ps0);
  connect(ps0->pin(1), esig->pin(0));

  netvector_t* ps0_vec = new netvector_t(osig_data_type, sel_need - 2, 0);
  NetNet* ps0_sig =
      new NetNet(scope, scope->local_symbol(), NetNet::TRI, ps0_vec);
  ps0_sig->local_flag(true);
  ps0_sig->set_line(loc);
  connect(ps0_sig->pin(0), ps0->pin(0));

  // osig = {..., esig[N-2:0]}
  connect(osig_cat->pin(1), ps0_sig->pin(0));

  // Create the part select esig[M-1:N-1]
  NetPartSelect* ps1 = new NetPartSelect(
      esig, sel_need - 1, sel_got - sel_need + 1, NetPartSelect::VP);
  ps1->set_line(loc);
  des->add_node(ps1);
  connect(ps1->pin(1), esig->pin(0));

  netvector_t* ps1_vec = new netvector_t(osig_data_type, sel_got - sel_need, 0);
  NetNet* ps1_sig =
      new NetNet(scope, scope->local_symbol(), NetNet::TRI, ps1_vec);
  ps1_sig->local_flag(true);
  ps1_sig->set_line(loc);
  connect(ps1_sig->pin(0), ps1->pin(0));

  // Create the reduction OR: | esig[M-1:N-1]
  NetUReduce* ered = new NetUReduce(scope, scope->local_symbol(),
                                    NetUReduce::OR, sel_got - sel_need + 1);
  ered->set_line(loc);
  des->add_node(ered);
  connect(ered->pin(1), ps1_sig->pin(0));

  NetNet* ered_sig = new NetNet(scope, scope->local_symbol(), NetNet::TRI,
                                &netvector_t::scalar_logic);
  ered_sig->local_flag(true);
  ered_sig->set_line(loc);
  connect(ered->pin(0), ered_sig->pin(0));

  // osig = { |esig[M-1:N-1], esig[N-2:0] }
  connect(osig_cat->pin(2), ered_sig->pin(0));

  return osig;
}

bool NetCase::synth_async(Design* des, NetScope* scope, NexusSet& nex_map,
                          NetBus& nex_out, NetBus& enables,
                          vector<mask_t>& bitmasks) {
  if (type() == NetCase::EQZ || type() == NetCase::EQX)
    return synth_async_casez_(des, scope, nex_map, nex_out, enables, bitmasks);

  // Special case: If the case expression is constant, then this
  // is a pattern where the guards are non-constant and tested
  // against a constant case. Handle this as chained conditions
  // instead.
  if (dynamic_cast<NetEConst*>(expr_))
    return synth_async_casez_(des, scope, nex_map, nex_out, enables, bitmasks);

  ivl_assert(*this, nex_map.size() == nex_out.pin_count());
  ivl_assert(*this, nex_map.size() == enables.pin_count());
  ivl_assert(*this, nex_map.size() == bitmasks.size());

  if (debug_synth2) {
    cerr << get_fileline() << ": NetCase::synth_async: "
         << "Selector expression: " << *expr_ << endl;
  }

  /* Synthesize the select expression. */
  NetNet* esig = expr_->synthesize(des, scope, expr_);

  unsigned sel_width = esig->vector_width();
  ivl_assert(*this, sel_width > 0);

  if (debug_synth2) {
    cerr << get_fileline() << ": NetCase::synth_async: "
         << "selector width (sel_width) = " << sel_width << endl;
  }

  vector<unsigned> mux_width(nex_out.pin_count());
  for (unsigned idx = 0; idx < nex_out.pin_count(); idx += 1) {
    mux_width[idx] = nex_map[idx].wid;
    if (debug_synth2) {
      cerr << get_fileline() << ": NetCase::synth_async: "
           << "idx=" << idx << ", mux_width[idx]=" << mux_width[idx] << endl;
    }
  }

  // The incoming nex_out is taken as the input for this
  // statement. Since there are collection of statements
  // that start at this same point, we save all these
  // inputs and reuse them for each statement. Unlink the
  // nex_out now, so we can hook up the mux outputs.
  NetBus statement_input(scope, nex_out.pin_count());
  for (unsigned idx = 0; idx < nex_out.pin_count(); idx += 1) {
    connect(statement_input.pin(idx), nex_out.pin(idx));
    nex_out.pin(idx).unlink();
    if (debug_synth2) {
      cerr << get_fileline() << ": NetCase::synth_async: "
           << "statement_input.pin(" << idx << "):" << endl;
      statement_input.pin(idx).dump_link(cerr, 8);
    }
  }

  /* Collect all the statements into a map of index to statement.
     The guard expression it evaluated to be the index of the mux
     value, and the statement is bound to that index. */

  unsigned long max_guard_value = 0;
  map<unsigned long, NetProc*> statement_map;
  NetProc* default_statement = 0;

  for (size_t item = 0; item < items_.size(); item += 1) {
    if (items_[item].guard == 0) {
      default_statement = items_[item].statement;
      continue;
    }

    NetEConst* ge = dynamic_cast<NetEConst*>(items_[item].guard);
    if (ge == 0) {
      cerr << items_[item].guard->get_fileline() << ": sorry: "
           << "variable case item expressions with a variable "
           << "case select expression are not supported in "
           << "synthesis. " << endl;
      des->errors += 1;
      return false;
    }
    ivl_assert(*this, ge);
    verinum gval = ge->value();

    unsigned long sel_idx = gval.as_ulong();

    if (statement_map[sel_idx]) {
      cerr << ge->get_fileline() << ": warning: duplicate case "
           << "value '" << sel_idx << "' detected. This case is "
           << "unreachable." << endl;
      delete items_[item].statement;
      items_[item].statement = 0;
      continue;
    }

    if (sel_idx > max_guard_value) max_guard_value = sel_idx;

    if (items_[item].statement) {
      statement_map[sel_idx] = items_[item].statement;
      continue;
    }

    // Handle the special case of an empty statement.
    statement_map[sel_idx] = this;
  }

  // The minimum selector width is the number of inputs that
  // are selected, rounded up to the nearest power of 2.
  unsigned sel_need = max(ceil(log2(max_guard_value + 1)), 1.0);

  // If the sel_width can select more than just the explicit
  // guard values, and there is a default statement, then adjust
  // the sel_need to allow for the implicit selections.
  if (default_statement && (sel_width > sel_need)) sel_need += 1;

  // The mux size is always an exact power of 2.
  if (sel_need >= 8 * sizeof(unsigned)) {
    cerr << get_fileline() << ": sorry: mux select width of " << sel_need
         << " bits is too large for synthesis." << endl;
    des->errors += 1;
    return false;
  }
  unsigned mux_size = 1U << sel_need;

  if (debug_synth2) {
    cerr << get_fileline() << ": NetCase::synth_async: "
         << "Adjusted mux_size is " << mux_size
         << " (max_guard_value=" << max_guard_value << ", sel_need=" << sel_need
         << ", sel_width=" << sel_width << ")." << endl;
  }

  if (sel_width > sel_need) {
    if (debug_synth2) {
      cerr << get_fileline() << ": NetCase::synth_async: "
           << "Selector is " << sel_width << " bits, "
           << "need only " << sel_need << " bits." << endl;
    }
    esig = mux_selector_reduce_width(des, scope, *this, esig, sel_need);
  }

  /* If there is a default clause, synthesize it once and we'll
     link it in wherever it is needed. If there isn't, create
     a dummy default to pass on the accumulated nex_out from
     preceding statements. */
  NetBus default_out(scope, nex_out.pin_count());
  NetBus default_ena(scope, nex_out.pin_count());
  vector<mask_t> default_masks(nex_out.pin_count());

  for (unsigned idx = 0; idx < nex_out.pin_count(); idx += 1) {
    connect(default_out.pin(idx), statement_input.pin(idx));
    connect(default_ena.pin(idx), scope->tie_lo());
  }

  if (default_statement) {
    bool flag = synth_async_block_substatement_(
        des, scope, nex_map, default_out, default_ena, default_masks,
        default_statement);
    if (!flag) return false;

    if (debug_synth2) {
      cerr << get_fileline() << ": NetCase::synth_async: "
           << "synthesize default clause at "
           << default_statement->get_fileline() << " is done." << endl;
    }
  }

  vector<NetMux*> out_mux(nex_out.pin_count());
  vector<NetMux*> ena_mux(nex_out.pin_count());
  vector<bool> full_case(nex_out.pin_count());
  for (size_t mdx = 0; mdx < nex_out.pin_count(); mdx += 1) {
    out_mux[mdx] = new NetMux(scope, scope->local_symbol(), mux_width[mdx],
                              mux_size, sel_need);
    des->add_node(out_mux[mdx]);

    // The select signal is already synthesized, and is
    // common for every mux of this case statement. Simply
    // hook it up.
    connect(out_mux[mdx]->pin_Sel(), esig->pin(0));

    // The outputs are in the nex_out, and connected to the
    // mux Result pins.
    connect(out_mux[mdx]->pin_Result(), nex_out.pin(mdx));

    // Make sure the output is now connected to a net. If
    // not, then create a fake one to carry the net-ness of
    // the pin.
    if (out_mux[mdx]->pin_Result().nexus()->pick_any_net() == 0) {
      ivl_variable_type_t mux_data_type = IVL_VT_LOGIC;
      netvector_t* tmp_vec =
          new netvector_t(mux_data_type, mux_width[mdx] - 1, 0);
      NetNet* tmp =
          new NetNet(scope, scope->local_symbol(), NetNet::WIRE, tmp_vec);
      tmp->local_flag(true);
      ivl_assert(*this, tmp->vector_width() != 0);
      connect(out_mux[mdx]->pin_Result(), tmp->pin(0));
    }

    // Create a mux for the enables, but don't hook it up
    // until we know we need it.
    ena_mux[mdx] =
        new NetMux(scope, scope->local_symbol(), 1, mux_size, sel_need);

    // Assume a full case to start with. We'll check this as
    // we synthesise each clause.
    full_case[mdx] = true;
  }

  for (unsigned idx = 0; idx < mux_size; idx += 1) {
    NetProc* stmt = statement_map[idx];
    if (stmt == 0) {
      ivl_assert(*this, default_out.pin_count() == out_mux.size());
      for (unsigned mdx = 0; mdx < nex_out.pin_count(); mdx += 1) {
        connect(out_mux[mdx]->pin_Data(idx), default_out.pin(mdx));
        connect(ena_mux[mdx]->pin_Data(idx), default_ena.pin(mdx));
        merge_parallel_masks(bitmasks[mdx], default_masks[mdx]);
        if (!default_ena.pin(mdx).is_linked(scope->tie_hi()))
          full_case[mdx] = false;
      }
      continue;
    }
    ivl_assert(*this, stmt);
    if (stmt == this) {
      // Handle the special case of an empty statement.
      ivl_assert(*this, statement_input.pin_count() == out_mux.size());
      for (unsigned mdx = 0; mdx < nex_out.pin_count(); mdx += 1) {
        connect(out_mux[mdx]->pin_Data(idx), statement_input.pin(mdx));
        connect(ena_mux[mdx]->pin_Data(idx), scope->tie_lo());
        bitmasks[mdx] = mask_t(mux_width[mdx], false);
        full_case[mdx] = false;
      }
      continue;
    }

    NetBus tmp_out(scope, nex_out.pin_count());
    NetBus tmp_ena(scope, nex_out.pin_count());
    for (unsigned mdx = 0; mdx < nex_out.pin_count(); mdx += 1) {
      connect(tmp_out.pin(mdx), statement_input.pin(mdx));
      connect(tmp_ena.pin(mdx), scope->tie_lo());
    }
    vector<mask_t> tmp_masks(nex_out.pin_count());
    bool flag = synth_async_block_substatement_(des, scope, nex_map, tmp_out,
                                                tmp_ena, tmp_masks, stmt);
    if (!flag) return false;

    for (size_t mdx = 0; mdx < nex_out.pin_count(); mdx += 1) {
      connect(out_mux[mdx]->pin_Data(idx), tmp_out.pin(mdx));
      connect(ena_mux[mdx]->pin_Data(idx), tmp_ena.pin(mdx));
      merge_parallel_masks(bitmasks[mdx], tmp_masks[mdx]);
      if (!tmp_ena.pin(mdx).is_linked(scope->tie_hi())) full_case[mdx] = false;
    }
  }

  for (unsigned mdx = 0; mdx < nex_out.pin_count(); mdx += 1) {
    // Optimize away the enable mux if we have a full case,
    // otherwise hook it up.
    if (full_case[mdx]) {
      connect(enables.pin(mdx), scope->tie_hi());
      delete ena_mux[mdx];
      continue;
    }

    des->add_node(ena_mux[mdx]);

    connect(ena_mux[mdx]->pin_Sel(), esig->pin(0));

    connect(enables.pin(mdx), ena_mux[mdx]->pin_Result());

    NetNet* tmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE,
                             &netvector_t::scalar_logic);
    tmp->local_flag(true);
    connect(ena_mux[mdx]->pin_Result(), tmp->pin(0));
  }
  return true;
}

/*
 * casez statements are hard to implement as a single wide mux because
 * the test doesn't really map to a select input. Instead, implement
 * it as a chain of binary muxes. This gives the synthesizer more
 * flexibility, and is more typically what is desired from a casez anyhow.
 */
bool NetCase::synth_async_casez_(Design* des, NetScope* scope,
                                 NexusSet& nex_map, NetBus& nex_out,
                                 NetBus& enables, vector<mask_t>& bitmasks) {
  ivl_assert(*this, nex_map.size() == nex_out.pin_count());
  ivl_assert(*this, nex_map.size() == enables.pin_count());
  ivl_assert(*this, nex_map.size() == bitmasks.size());

  /* Synthesize the select expression. */
  NetNet* esig = expr_->synthesize(des, scope, expr_);

  unsigned sel_width = esig->vector_width();
  ivl_assert(*this, sel_width > 0);

  vector<unsigned> mux_width(nex_out.pin_count());
  for (unsigned idx = 0; idx < nex_out.pin_count(); idx += 1) {
    mux_width[idx] = nex_map[idx].wid;
    if (debug_synth2) {
      cerr << get_fileline() << ": NetCase::synth_async_casez_: "
           << "idx=" << idx << ", mux_width[idx]=" << mux_width[idx] << endl;
    }
  }

  // The incoming nex_out is taken as the input for this
  // statement. Since there are collection of statements
  // that start at this same point, we save all these
  // inputs and reuse them for each statement. Unlink the
  // nex_out now, so we can hook up the mux outputs.
  NetBus statement_input(scope, nex_out.pin_count());
  for (unsigned idx = 0; idx < nex_out.pin_count(); idx += 1) {
    connect(statement_input.pin(idx), nex_out.pin(idx));
    nex_out.pin(idx).unlink();
    if (debug_synth2) {
      cerr << get_fileline() << ": NetCase::synth_async_casez_: "
           << "statement_input.pin(" << idx << "):" << endl;
      statement_input.pin(idx).dump_link(cerr, 8);
    }
  }

  // Look for a default statement.
  NetProc* default_statement = 0;
  for (size_t item = 0; item < items_.size(); item += 1) {
    if (items_[item].guard != 0) continue;

    ivl_assert(*this, default_statement == 0);
    default_statement = items_[item].statement;
  }

  /* If there is a default clause, synthesize it once and we'll
     link it in wherever it is needed. If there isn't, create
     a dummy default to pass on the accumulated nex_out from
     preceding statements. */
  NetBus default_out(scope, nex_out.pin_count());

  for (unsigned idx = 0; idx < default_out.pin_count(); idx += 1)
    connect(default_out.pin(idx), statement_input.pin(idx));

  if (default_statement) {
    bool flag = synth_async_block_substatement_(
        des, scope, nex_map, default_out, enables, bitmasks, default_statement);
    if (!flag) return false;

    if (debug_synth2) {
      cerr << get_fileline() << ": NetCase::synth_async_casez_: "
           << "synthesize default clause at "
           << default_statement->get_fileline() << " is done." << endl;
    }
  }

  netvector_t* condit_type = new netvector_t(IVL_VT_LOGIC, 0, 0);

  NetCaseCmp::kind_t case_kind = NetCaseCmp::EEQ;
  switch (type()) {
    case NetCase::EQ:
      case_kind = NetCaseCmp::EEQ;
      break;
    case NetCase::EQX:
      case_kind = NetCaseCmp::XEQ;
      break;
    case NetCase::EQZ:
      case_kind = NetCaseCmp::ZEQ;
      break;
    default:
      assert(0);
  }

  // Process the items from last to first. We generate a
  // true/false mux, with the select being the comparison of
  // the case select with the guard expression. The true input
  // (data1) is the current statement, and the false input is
  // the result of a later statement.
  vector<NetMux*> prev_mux(nex_out.pin_count());
  for (size_t idx = 0; idx < items_.size(); idx += 1) {
    size_t item = items_.size() - idx - 1;
    if (items_[item].guard == 0) continue;

    NetProc* stmt = items_[item].statement;
    ivl_assert(*this, stmt);

    NetExpr* guard_expr = items_[item].guard;
    NetNet* guard = guard_expr->synthesize(des, scope, guard_expr);

    NetCaseCmp* condit_dev =
        new NetCaseCmp(scope, scope->local_symbol(), sel_width, case_kind);
    des->add_node(condit_dev);
    condit_dev->set_line(*this);
    // Note that the expression that may have wildcards must
    // go in the pin(2) input. This is the definition of the
    // NetCaseCmp statement.
    connect(condit_dev->pin(1), esig->pin(0));
    connect(condit_dev->pin(2), guard->pin(0));

    NetNet* condit =
        new NetNet(scope, scope->local_symbol(), NetNet::TRI, condit_type);
    condit->set_line(*this);
    condit->local_flag(true);
    connect(condit_dev->pin(0), condit->pin(0));

    // Synthesize the guarded statement.
    NetBus tmp_out(scope, nex_out.pin_count());
    NetBus tmp_ena(scope, nex_out.pin_count());
    vector<mask_t> tmp_masks(nex_out.pin_count());

    for (unsigned pdx = 0; pdx < nex_out.pin_count(); pdx += 1)
      connect(tmp_out.pin(pdx), statement_input.pin(pdx));

    synth_async_block_substatement_(des, scope, nex_map, tmp_out, tmp_ena,
                                    tmp_masks, stmt);

    NetBus prev_ena(scope, nex_out.pin_count());
    for (unsigned mdx = 0; mdx < nex_out.pin_count(); mdx += 1) {
      NetMux* mux =
          new NetMux(scope, scope->local_symbol(), mux_width[mdx], 2, 1);
      des->add_node(mux);
      mux->set_line(*this);
      connect(mux->pin_Sel(), condit->pin(0));

      connect(mux->pin_Data(1), tmp_out.pin(mdx));

      // If there is a previous mux, then use that as the
      // false clause input. Otherwise, use the default.
      if (prev_mux[mdx])
        connect(mux->pin_Data(0), prev_mux[mdx]->pin_Result());
      else
        connect(mux->pin_Data(0), default_out.pin(mdx));

      // Make a NetNet for the result.
      ivl_variable_type_t mux_data_type = IVL_VT_LOGIC;
      netvector_t* tmp_vec =
          new netvector_t(mux_data_type, mux_width[mdx] - 1, 0);
      NetNet* tmp =
          new NetNet(scope, scope->local_symbol(), NetNet::WIRE, tmp_vec);
      tmp->local_flag(true);
      tmp->set_line(*this);
      ivl_assert(*this, tmp->vector_width() != 0);
      connect(mux->pin_Result(), tmp->pin(0));

      // This mux becomes the "false" input to the next mux.
      prev_mux[mdx] = mux;

      connect(prev_ena.pin(mdx), enables.pin(mdx));
      enables.pin(mdx).unlink();

      multiplex_enables(des, scope, condit, tmp_ena.pin(mdx), prev_ena.pin(mdx),
                        enables.pin(mdx));

      merge_parallel_masks(bitmasks[mdx], tmp_masks[mdx]);
    }
  }

  // Connect the last mux to the output.
  for (size_t mdx = 0; mdx < prev_mux.size(); mdx += 1)
    connect(prev_mux[mdx]->pin_Result(), nex_out.pin(mdx));

  return true;
}

/*
 * A condit statement (if (cond) ... else ... ;) infers an A-B mux,
 * with the cond expression acting as a select input. If the cond
 * expression is true, the if_ clause is selected, and if false, the
 * else_ clause is selected.
 */
bool NetCondit::synth_async(Design* des, NetScope* scope, NexusSet& nex_map,
                            NetBus& nex_out, NetBus& enables,
                            vector<mask_t>& bitmasks) {
  // Handle the unlikely case that both clauses are empty.
  if ((if_ == 0) && (else_ == 0)) return true;

  ivl_assert(*this, nex_map.size() == nex_out.pin_count());
  ivl_assert(*this, nex_map.size() == enables.pin_count());
  ivl_assert(*this, nex_map.size() == bitmasks.size());

  // Synthesize the condition. This will act as a select signal
  // for a binary mux.
  NetNet* ssig = expr_->synthesize(des, scope, expr_);
  ivl_assert(*this, ssig);

  // The incoming nex_out is taken as the input for this
  // statement. Since there are two statements that start
  // at this same point, we save all these inputs and reuse
  // them for both statements. Unlink the nex_out now, so
  // we can hook up the mux outputs.
  NetBus statement_input(scope, nex_out.pin_count());
  for (unsigned idx = 0; idx < nex_out.pin_count(); idx += 1) {
    connect(statement_input.pin(idx), nex_out.pin(idx));
    nex_out.pin(idx).unlink();
    if (debug_synth2) {
      cerr << get_fileline() << ": NetCondit::synth_async: "
           << "statement_input.pin(" << idx << "):" << endl;
      statement_input.pin(idx).dump_link(cerr, 8);
    }
  }

  NetBus a_out(scope, nex_out.pin_count());
  NetBus a_ena(scope, nex_out.pin_count());
  vector<mask_t> a_masks(nex_out.pin_count());
  if (if_) {
    if (debug_synth2) {
      cerr << get_fileline() << ": NetCondit::synth_async: "
           << "Synthesize if clause at " << if_->get_fileline() << endl;
    }

    for (unsigned idx = 0; idx < a_out.pin_count(); idx += 1) {
      connect(a_out.pin(idx), statement_input.pin(idx));
    }

    bool flag = synth_async_block_substatement_(des, scope, nex_map, a_out,
                                                a_ena, a_masks, if_);
    if (!flag) return false;

  } else {
    for (unsigned idx = 0; idx < a_out.pin_count(); idx += 1) {
      connect(a_out.pin(idx), statement_input.pin(idx));
      connect(a_ena.pin(idx), scope->tie_lo());
    }
  }

  NetBus b_out(scope, nex_out.pin_count());
  NetBus b_ena(scope, nex_out.pin_count());
  vector<mask_t> b_masks(nex_out.pin_count());
  if (else_) {
    if (debug_synth2) {
      cerr << get_fileline() << ": NetCondit::synth_async: "
           << "Synthesize else clause at " << else_->get_fileline() << endl;
    }

    for (unsigned idx = 0; idx < b_out.pin_count(); idx += 1) {
      connect(b_out.pin(idx), statement_input.pin(idx));
    }

    bool flag = synth_async_block_substatement_(des, scope, nex_map, b_out,
                                                b_ena, b_masks, else_);
    if (!flag) return false;

  } else {
    for (unsigned idx = 0; idx < b_out.pin_count(); idx += 1) {
      connect(b_out.pin(idx), statement_input.pin(idx));
      connect(b_ena.pin(idx), scope->tie_lo());
    }
  }

  /* The nex_out output, a_out input, and b_out input all have the
     same pin count (usually, but not always 1) because they are
     net arrays of the same dimension. The for loop below creates
     a NetMux for each pin of the output. (Note that pins may
     be, in fact usually are, vectors.) */

  for (unsigned idx = 0; idx < nex_out.pin_count(); idx += 1) {
    bool a_driven = a_out.pin(idx).nexus()->pick_any_net();
    bool b_driven = b_out.pin(idx).nexus()->pick_any_net();
    if (!a_driven && !b_driven) {
      connect(nex_out.pin(idx), statement_input.pin(idx));
      continue;
    }

    merge_parallel_masks(bitmasks[idx], a_masks[idx]);
    merge_parallel_masks(bitmasks[idx], b_masks[idx]);

    // If one clause is empty and the other clause unconditionally
    // drives all bits of the vector, we can rely on the enable
    // to prevent the flip-flop or latch updating when the empty
    // clause is selected, and hence don't need a mux.
    if (!a_driven && all_bits_driven(b_masks[idx])) {
      connect(nex_out.pin(idx), b_out.pin(idx));
      continue;
    }
    if (!b_driven && all_bits_driven(a_masks[idx])) {
      connect(nex_out.pin(idx), a_out.pin(idx));
      continue;
    }

    // Guess the mux type from the type of the output.
    ivl_variable_type_t mux_data_type = IVL_VT_LOGIC;
    if (NetNet* tmp = nex_out.pin(idx).nexus()->pick_any_net()) {
      mux_data_type = tmp->data_type();
    }

    unsigned mux_off = 0;
    unsigned mux_width = nex_map[idx].wid;

    if (debug_synth2) {
      cerr << get_fileline() << ": NetCondit::synth_async: "
           << "Calculated mux_width=" << mux_width << endl;
    }

    NetPartSelect* apv = detect_partselect_lval(a_out.pin(idx));
    if (debug_synth2 && apv) {
      cerr << get_fileline() << ": NetCondit::synth_async: "
           << "Assign-to-part apv base=" << apv->base()
           << ", width=" << apv->width() << endl;
    }

    NetPartSelect* bpv = detect_partselect_lval(b_out.pin(idx));
    if (debug_synth2 && bpv) {
      cerr << get_fileline() << ": NetCondit::synth_async: "
           << "Assign-to-part bpv base=" << bpv->base()
           << ", width=" << bpv->width() << endl;
    }

    unsigned mux_lwidth = mux_width;
    ivl_assert(*this, mux_width != 0);

    if (apv && bpv && apv->width() == bpv->width() &&
        apv->base() == bpv->base()) {
      // The a and b sides are both assigning to the
      // same bits of the output, so we can use that to
      // create a much narrower mux that only
      // manipulates the width of the part.
      mux_width = apv->width();
      mux_off = apv->base();
      a_out.pin(idx).unlink();
      b_out.pin(idx).unlink();
      connect(a_out.pin(idx), apv->pin(0));
      connect(b_out.pin(idx), bpv->pin(0));
      delete apv;
      delete bpv;
    } else {
      // The part selects are of no use. Forget them.
      apv = 0;
      bpv = 0;
    }

    NetMux* mux = new NetMux(scope, scope->local_symbol(), mux_width, 2, 1);
    mux->set_line(*this);
    des->add_node(mux);

    netvector_t* tmp_type = 0;
    if (mux_width == 1)
      tmp_type = new netvector_t(mux_data_type);
    else
      tmp_type = new netvector_t(mux_data_type, mux_width - 1, 0);

    // Bind some temporary signals to carry pin type.
    NetNet* otmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE,
                              NetNet::not_an_array, tmp_type);
    otmp->local_flag(true);
    otmp->set_line(*this);
    connect(mux->pin_Result(), otmp->pin(0));

    connect(mux->pin_Sel(), ssig->pin(0));
    connect(mux->pin_Data(1), a_out.pin(idx));
    connect(mux->pin_Data(0), b_out.pin(idx));

    // If we are only muxing a part of the output vector, make a
    // NetSubstitute to blend the mux output with the accumulated
    // output from previous statements.
    if (mux_width < mux_lwidth) {
      tmp_type = new netvector_t(mux_data_type, mux_lwidth - 1, 0);

      NetNet* itmp = statement_input.pin(idx).nexus()->pick_any_net();
      if (itmp == 0) {
        itmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE,
                          NetNet::not_an_array, tmp_type);
        itmp->local_flag(true);
        itmp->set_line(*this);
        connect(itmp->pin(0), statement_input.pin(idx));
      }

      NetNet* tmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE,
                               NetNet::not_an_array, tmp_type);
      tmp->local_flag(true);
      tmp->set_line(*this);
      NetSubstitute* ps = new NetSubstitute(itmp, otmp, mux_lwidth, mux_off);
      des->add_node(ps);
      connect(ps->pin(0), tmp->pin(0));
      otmp = tmp;
    }

    connect(nex_out.pin(idx), otmp->pin(0));
  }

  for (unsigned idx = 0; idx < nex_out.pin_count(); idx += 1) {
    multiplex_enables(des, scope, ssig, a_ena.pin(idx), b_ena.pin(idx),
                      enables.pin(idx));
  }

  return true;
}

bool NetEvWait::synth_async(Design* des, NetScope* scope, NexusSet& nex_map,
                            NetBus& nex_out, NetBus& enables,
                            vector<mask_t>& bitmasks) {
  bool flag =
      statement_->synth_async(des, scope, nex_map, nex_out, enables, bitmasks);
  return flag;
}

bool NetForLoop::synth_async(Design* des, NetScope* scope, NexusSet& nex_map,
                             NetBus& nex_out, NetBus& enables,
                             vector<mask_t>& bitmasks) {
  if (debug_synth2) {
    cerr << get_fileline() << ": NetForLoop::synth_async: "
         << "Index variable is " << index_->name() << endl;
    cerr << get_fileline() << ": NetForLoop::synth_async: "
         << "Initialization expression: " << *init_expr_ << endl;
  }

  // Get the step assignment statement and break it into the
  // l-value (should be the index) and the r-value, which is the
  // step expressions.
  NetAssign* step_assign = dynamic_cast<NetAssign*>(step_statement_);
  char assign_operator = step_assign->assign_operator();
  ivl_assert(*this, step_assign);
  NetExpr* step_expr = step_assign->rval();

  // Tell the scope that this index value is like a genvar.
  LocalVar index_var;
  index_var.nwords = 0;

  map<perm_string, LocalVar> index_args;

  // Calculate the initial value for the index.
  index_var.value = init_expr_->evaluate_function(*this, index_args);
  ivl_assert(*this, index_var.value);
  index_args[index_->name()] = index_var;

  for (;;) {
    // Evaluate the condition expression. If it is false,
    // then we are going to break out of this synthesis loop.
    NetExpr* tmp = condition_->evaluate_function(*this, index_args);
    ivl_assert(*this, tmp);

    long cond_value;
    bool rc = eval_as_long(cond_value, tmp);
    ivl_assert(*this, rc);
    delete tmp;
    if (!cond_value) break;

    scope->genvar_tmp = index_->name();
    rc = eval_as_long(scope->genvar_tmp_val, index_var.value);
    ivl_assert(*this, rc);

    if (debug_synth2) {
      cerr << get_fileline() << ": NetForLoop::synth_async: "
           << "Synthesis iteration with " << index_->name() << "="
           << *index_var.value << endl;
    }

    // Synthesize the iterated expression. Stash the loop
    // index value so that the substatements can see this
    // value and use it during its own synthesis.
    ivl_assert(*this, scope->loop_index_tmp.empty());
    scope->loop_index_tmp = index_args;

    NetBus tmp_ena(scope, nex_out.pin_count());
    vector<mask_t> tmp_masks(nex_out.pin_count());

    rc = synth_async_block_substatement_(des, scope, nex_map, nex_out, tmp_ena,
                                         tmp_masks, statement_);

    for (unsigned idx = 0; idx < nex_out.pin_count(); idx += 1) {
      merge_sequential_enables(des, scope, enables.pin(idx), tmp_ena.pin(idx));
      merge_sequential_masks(bitmasks[idx], tmp_masks[idx]);
    }

    scope->loop_index_tmp.clear();

    // Evaluate the step_expr to generate the next index value.
    tmp = step_expr->evaluate_function(*this, index_args);
    ivl_assert(*this, tmp);

    // If there is an assign_operator, then replace the
    // index_var.value with (value <op> tmp) and evaluate
    // that to get the next value. "value" is the existing
    // value, and "tmp" is the step value. We are replacing
    // (value += tmp) with (value = value + tmp) and
    // evaluating it.
    switch (assign_operator) {
      case 0:
        break;
      case '+':
      case '-':
        index_var.value =
            new NetEBAdd(assign_operator, tmp, index_var.value, 32, true);
        tmp = index_var.value->evaluate_function(*this, index_args);
        break;

      default:
        cerr << get_fileline() << ": internal error: "
             << "NetForLoop::synth_async: What to do with assign_operator="
             << assign_operator << endl;
        ivl_assert(*this, 0);
    }
    delete index_var.value;
    index_var.value = tmp;
    index_args[index_->name()] = index_var;
  }

  delete index_var.value;

  return true;
}

/*
 * This method is called when the process is shown to be
 * asynchronous. Figure out the nexus set of outputs from this
 * process, and pass that to the synth_async method for the statement
 * of the process. The statement will connect its output to the
 * nex_out set, using the nex_map as a guide. Starting from the top,
 * the nex_map is the same as the nex_map.
 */
bool NetProcTop::synth_async(Design* des) {
  NexusSet nex_set;
  statement_->nex_output(nex_set);

  if (debug_synth2) {
    cerr << get_fileline() << ": NetProcTop::synth_async: "
         << "Process has " << nex_set.size() << " outputs." << endl;
  }

  NetBus nex_out(scope(), nex_set.size());
  NetBus enables(scope(), nex_set.size());
  vector<NetProc::mask_t> bitmasks(nex_set.size());

  // Save links to the initial nex_out. These will be used later
  // to detect floating part-substitute and mux inputs that need
  // to be tied off.
  NetBus nex_in(scope(), nex_out.pin_count());
  for (unsigned idx = 0; idx < nex_out.pin_count(); idx += 1)
    connect(nex_in.pin(idx), nex_out.pin(idx));

  bool flag = statement_->synth_async(des, scope(), nex_set, nex_out, enables,
                                      bitmasks);
  if (!flag) return false;

  flag = tie_off_floating_inputs_(des, nex_set, nex_in, bitmasks, false);
  if (!flag) return false;

  for (unsigned idx = 0; idx < nex_set.size(); idx += 1) {
    if (enables.pin(idx).is_linked(scope()->tie_hi())) {
      connect(nex_set[idx].lnk, nex_out.pin(idx));
    } else {
      cerr << get_fileline() << ": warning: "
           << "A latch has been inferred for '"
           << nex_set[idx].lnk.nexus()->pick_any_net()->name() << "'." << endl;

      if (enables.pin(idx).nexus()->pick_any_net()->local_flag()) {
        cerr << get_fileline()
             << ": warning: The latch "
                "enable is connected to a synthesized "
                "expression. The latch may be sensitive "
                "to glitches."
             << endl;
      }

      if (debug_synth2) {
        cerr << get_fileline() << ": debug: "
             << "Top level making a " << nex_set[idx].wid << "-wide "
             << "NetLatch device." << endl;
      }

      NetLatch* latch =
          new NetLatch(scope(), scope()->local_symbol(), nex_set[idx].wid);
      des->add_node(latch);
      latch->set_line(*this);

      NetNet* tmp = nex_out.pin(idx).nexus()->pick_any_net();
      tmp->set_line(*this);
      assert(tmp);

      tmp = crop_to_width(des, tmp, latch->width());

      connect(nex_set[idx].lnk, latch->pin_Q());
      connect(tmp->pin(0), latch->pin_Data());

      assert(enables.pin(idx).is_linked());
      connect(enables.pin(idx), latch->pin_Enable());
    }
  }

  synthesized_design_ = des;
  return true;
}

bool NetProc::synth_sync(Design* des, NetScope* scope, bool& /* ff_negedge */,
                         NetNet* /* ff_clk */, NetBus& ff_ce,
                         NetBus& /* ff_aclr*/, NetBus& /* ff_aset*/,
                         vector<verinum>& /*ff_aset_value*/, NexusSet& nex_map,
                         NetBus& nex_out, vector<mask_t>& bitmasks,
                         const vector<NetEvProbe*>& events) {
  if (events.size() > 0) {
    cerr << get_fileline() << ": error: Events are unaccounted"
         << " for in process synthesis." << endl;
    des->errors += 1;
  }

  if (debug_synth2) {
    cerr << get_fileline() << ": NetProc::synth_sync: "
         << "This statement is an async input to a sync process." << endl;
  }

  /* Synthesize the input to the DFF. */
  return synth_async(des, scope, nex_map, nex_out, ff_ce, bitmasks);
}

/*
 * This method is called when a block is encountered near the surface
 * of a synchronous always statement. For example, this code will be
 * invoked for input like this:
 *
 *     always @(posedge clk...) begin
 *	     <statement1>
 *	     <statement2>
 *	     ...
 *     end
 *
 * This needs to be split into a DFF bank for each statement, because
 * the statements may each infer different reset and enables signals.
 */
bool NetBlock::synth_sync(Design* des, NetScope* scope, bool& ff_negedge,
                          NetNet* ff_clk, NetBus& ff_ce, NetBus& ff_aclr,
                          NetBus& ff_aset, vector<verinum>& ff_aset_value,
                          NexusSet& nex_map, NetBus& nex_out,
                          vector<mask_t>& bitmasks,
                          const vector<NetEvProbe*>& events_in) {
  if (debug_synth2) {
    cerr << get_fileline() << ": NetBlock::synth_sync: "
         << "Examine this block for synchronous logic." << endl;
  }

  if (last_ == 0) {
    return true;
  }

  bool flag = true;

  NetProc* cur = last_;
  do {
    cur = cur->next_;

    // Create a temporary nex_map for the substatement.
    NexusSet tmp_map;
    cur->nex_output(tmp_map);

    // Create temporary variables to collect the output from the synthesis.
    NetBus tmp_out(scope, tmp_map.size());
    NetBus tmp_ce(scope, tmp_map.size());
    vector<mask_t> tmp_masks(tmp_map.size());

    // Map (and move) the accumulated nex_out for this block
    // to the version that we can pass to the next statement.
    // We will move the result back later.
    for (unsigned idx = 0; idx < tmp_out.pin_count(); idx += 1) {
      unsigned ptr = nex_map.find_nexus(tmp_map[idx]);
      ivl_assert(*this, ptr < nex_out.pin_count());
      connect(tmp_out.pin(idx), nex_out.pin(ptr));
      nex_out.pin(ptr).unlink();
    }

    /* Now go on with the synchronous synthesis for this
       subset of the statement. The tmp_map is the output
       nexa that we expect, and the tmp_out is where we want
       those outputs connected. */
    bool ok_flag = cur->synth_sync(des, scope, ff_negedge, ff_clk, tmp_ce,
                                   ff_aclr, ff_aset, ff_aset_value, tmp_map,
                                   tmp_out, tmp_masks, events_in);
    flag = flag && ok_flag;

    if (ok_flag == false) continue;

    // Now map the output from the substatement back to the
    // outputs for this block.
    for (unsigned idx = 0; idx < tmp_out.pin_count(); idx += 1) {
      unsigned ptr = nex_map.find_nexus(tmp_map[idx]);
      ivl_assert(*this, ptr < nex_out.pin_count());
      connect(nex_out.pin(ptr), tmp_out.pin(idx));

      merge_sequential_enables(des, scope, ff_ce.pin(ptr), tmp_ce.pin(idx));

      merge_sequential_masks(bitmasks[ptr], tmp_masks[idx]);
    }

  } while (cur != last_);

  if (debug_synth2) {
    cerr << get_fileline() << ": NetBlock::synth_sync: "
         << "Done Examining this block for synchronous logic." << endl;
  }

  return flag;
}

/*
 * This method handles the case where I find a conditional near the
 * surface of a synchronous thread. This conditional can be a CE or an
 * asynchronous set/reset, depending on whether the pin of the
 * expression is connected to an event, or not.
 */
bool NetCondit::synth_sync(Design* des, NetScope* scope, bool& ff_negedge,
                           NetNet* ff_clk, NetBus& ff_ce, NetBus& ff_aclr,
                           NetBus& ff_aset, vector<verinum>& ff_aset_value,
                           NexusSet& nex_map, NetBus& nex_out,
                           vector<mask_t>& bitmasks,
                           const vector<NetEvProbe*>& events_in) {
  /* First try to turn the condition expression into an
     asynchronous set/reset. If the condition expression has
     inputs that are included in the sensitivity list, then it
     is likely intended as an asynchronous input. */

  NexusSet* expr_input = expr_->nex_input();
  assert(expr_input);
  for (unsigned idx = 0; idx < events_in.size(); idx += 1) {
    NetEvProbe* ev = events_in[idx];
    NexusSet pin_set;
    pin_set.add(ev->pin(0).nexus(), 0, 0);

    if (!expr_input->contains(pin_set)) continue;

    // Synthesize the set/reset input expression.
    NetNet* rst = expr_->synthesize(des, scope, expr_);
    ivl_assert(*this, rst->pin_count() == 1);

    // Check that the edge used on the set/reset input is correct.
    switch (ev->edge()) {
      case NetEvProbe::POSEDGE:
        if (ev->pin(0).nexus() != rst->pin(0).nexus()) {
          cerr << get_fileline() << ": error: "
               << "Condition for posedge asynchronous set/reset "
               << "must exactly match the event expression." << endl;
          des->errors += 1;
          return false;
        }
        break;
      case NetEvProbe::NEGEDGE: {
        bool is_inverter = false;
        NetNode* node = rst->pin(0).nexus()->pick_any_node();
        if (NetLogic* gate = dynamic_cast<NetLogic*>(node)) {
          if (gate->type() == NetLogic::NOT) is_inverter = true;
        }
        if (NetUReduce* gate = dynamic_cast<NetUReduce*>(node)) {
          if (gate->type() == NetUReduce::NOR) is_inverter = true;
        }
        if (!is_inverter || ev->pin(0).nexus() != node->pin(1).nexus()) {
          cerr << get_fileline() << ": error: "
               << "Condition for negedge asynchronous set/reset must be "
               << "a simple inversion of the event expression." << endl;
          des->errors += 1;
          return false;
        }
        break;
      }
      default:
        cerr << get_fileline() << ": error: "
             << "Asynchronous set/reset event must be "
             << "edge triggered." << endl;
        des->errors += 1;
        return false;
    }

    // Synthesize the true clause to figure out what kind of
    // set/reset we have. This should synthesize down to a
    // constant. If not, we have an asynchronous LOAD, a
    // very different beast.
    ivl_assert(*this, if_);
    NetBus tmp_out(scope, nex_out.pin_count());
    NetBus tmp_ena(scope, nex_out.pin_count());
    vector<mask_t> tmp_masks(nex_out.pin_count());
    bool flag =
        if_->synth_async(des, scope, nex_map, tmp_out, tmp_ena, tmp_masks);
    if (!flag) return false;

    ivl_assert(*this, tmp_out.pin_count() == ff_aclr.pin_count());
    ivl_assert(*this, tmp_out.pin_count() == ff_aset.pin_count());

    for (unsigned pin = 0; pin < tmp_out.pin_count(); pin += 1) {
      Nexus* rst_nex = tmp_out.pin(pin).nexus();

      if (!all_bits_driven(tmp_masks[pin])) {
        cerr << get_fileline() << ": sorry: Not all bits of '"
             << nex_map[idx].lnk.nexus()->pick_any_net()->name()
             << "' are asynchronously set or reset. This is "
             << "not currently supported in synthesis." << endl;
        des->errors += 1;
        return false;
      }

      if (!rst_nex->drivers_constant() ||
          !tmp_ena.pin(pin).is_linked(scope->tie_hi())) {
        cerr << get_fileline() << ": sorry: Asynchronous load "
             << "is not currently supported in synthesis." << endl;
        des->errors += 1;
        return false;
      }

      if (ff_aclr.pin(pin).is_linked() || ff_aset.pin(pin).is_linked()) {
        cerr << get_fileline()
             << ": sorry: More than "
                "one asynchronous set/reset clause is "
                "not currently supported in synthesis."
             << endl;
        des->errors += 1;
        return false;
      }

      verinum rst_drv = rst_nex->driven_vector();

      verinum zero(verinum::V0, rst_drv.len());
      verinum ones(verinum::V1, rst_drv.len());

      if (rst_drv == zero) {
        // Don't yet support multiple asynchronous reset inputs.
        ivl_assert(*this, !ff_aclr.pin(pin).is_linked());

        ivl_assert(*this, rst->pin_count() == 1);
        connect(ff_aclr.pin(pin), rst->pin(0));

      } else {
        // Don't yet support multiple asynchronous set inputs.
        ivl_assert(*this, !ff_aset.pin(pin).is_linked());

        ivl_assert(*this, rst->pin_count() == 1);
        connect(ff_aset.pin(pin), rst->pin(0));
        if (rst_drv != ones) ff_aset_value[pin] = rst_drv;
      }
    }

    if (else_ == 0) return true;

    vector<NetEvProbe*> events;
    for (unsigned jdx = 0; jdx < events_in.size(); jdx += 1) {
      if (jdx != idx) events.push_back(events_in[jdx]);
    }
    return else_->synth_sync(des, scope, ff_negedge, ff_clk, ff_ce, ff_aclr,
                             ff_aset, ff_aset_value, nex_map, nex_out, bitmasks,
                             events);
  }

  delete expr_input;

#if 0
	/* Detect the case that this is a *synchronous* set/reset. It
	   is not asynchronous because we know the condition is not
	   included in the sensitivity list, but if the if_ case is
	   constant (has no inputs) then we can model this as a
	   synchronous set/reset.

	   This is only synchronous set/reset if there is a true and a
	   false clause, and no inputs. The "no inputs" requirement is
	   met if the assignments are of all constant values. */
      assert(if_ != 0);
      NexusSet*a_set = if_->nex_input();

      if ((a_set->count() == 0) && if_ && else_) {

	    NetNet*rst = expr_->synthesize(des);
	    assert(rst->pin_count() == 1);

	      /* Synthesize the true clause to figure out what
		 kind of set/reset we have. */
	    NetNet*asig = new NetNet(scope, scope->local_symbol(),
				     NetNet::WIRE, nex_map->pin_count());
	    asig->local_flag(true);
	    bool flag = if_->synth_async(des, scope, nex_map, asig);

	    if (!flag) {
		  /* This path leads nowhere */
		  delete asig;
	    } else {
		  assert(asig->pin_count() == ff->width());

		    /* Collect the set/reset value into a verinum. If
		       this turns out to be entirely 0 values, then
		       use the Sclr input. Otherwise, use the Aset
		       input and save the set value. */
		  verinum tmp (verinum::V0, ff->width());
		  for (unsigned bit = 0 ;  bit < ff->width() ;	bit += 1) {

			assert(asig->pin(bit).nexus()->drivers_constant());
			tmp.set(bit, asig->pin(bit).nexus()->driven_value());
		  }

		  assert(tmp.is_defined());
		  if (tmp.is_zero()) {
			connect(ff->pin_Sclr(), rst->pin(0));

		  } else {
			connect(ff->pin_Sset(), rst->pin(0));
			ff->sset_value(tmp);
		  }

		  delete a_set;

		  assert(else_ != 0);
		  flag = else_->synth_sync(des, scope, ff, nex_map,
					   nex_out, svector<NetEvProbe*>(0))
			&& flag;
		  DEBUG_SYNTH2_EXIT("NetCondit",flag)
		  return flag;
	    }
      }

      delete a_set;
#endif

#if 0
	/* This gives a false positive for strange coding styles,
	   such as ivltests/conditsynth3.v. */

	/* Failed to find an asynchronous set/reset, so any events
	   input are probably in error. */
      if (events_in.size() > 0) {
	    cerr << get_fileline() << ": error: Events are unaccounted"
		 << " for in process synthesis." << endl;
	    des->errors += 1;
      }
#endif

  return synth_async(des, scope, nex_map, nex_out, ff_ce, bitmasks);
}

bool NetEvWait::synth_sync(Design* des, NetScope* scope, bool& ff_negedge,
                           NetNet* ff_clk, NetBus& ff_ce, NetBus& ff_aclr,
                           NetBus& ff_aset, vector<verinum>& ff_aset_value,
                           NexusSet& nex_map, NetBus& nex_out,
                           vector<mask_t>& bitmasks,
                           const vector<NetEvProbe*>& events_in) {
  if (debug_synth2) {
    cerr << get_fileline() << ": NetEvWait::synth_sync: "
         << "Synchronous process an event statement." << endl;
  }

  if (events_in.size() > 0) {
    cerr << get_fileline() << ": error: Events are unaccounted"
         << " for in process synthesis." << endl;
    des->errors += 1;
  }

  assert(events_in.size() == 0);

  /* This can't be other than one unless there are named events,
     which I cannot synthesize. */
  ivl_assert(*this, events_.size() == 1);
  NetEvent* ev = events_[0];

  assert(ev->nprobe() >= 1);
  vector<NetEvProbe*> events(ev->nprobe() - 1);

  /* Get the input set from the substatement. This will be used
     to figure out which of the probes is the clock. */
  NexusSet* statement_input = statement_->nex_input();

  /* Search for a clock input. The clock input is the edge event
     that is not also an input to the substatement. */
  NetEvProbe* pclk = 0;
  unsigned event_idx = 0;
  for (unsigned idx = 0; idx < ev->nprobe(); idx += 1) {
    NetEvProbe* tmp = ev->probe(idx);
    assert(tmp->pin_count() == 1);

    NexusSet tmp_nex;
    tmp_nex.add(tmp->pin(0).nexus(), 0, 0);

    if (!statement_input->contains(tmp_nex)) {
      if (pclk != 0) {
        cerr << get_fileline() << ": error: Too many "
             << "clocks for synchronous logic." << endl;
        cerr << get_fileline() << ":	  : Perhaps an"
             << " asynchronous set/reset is misused?" << endl;
        des->errors += 1;
      }
      pclk = tmp;

    } else {
      events[event_idx++] = tmp;
    }
  }

  if (pclk == 0) {
    cerr << get_fileline() << ": error: None of the edges"
         << " are valid clock inputs." << endl;
    cerr << get_fileline() << ":      : Perhaps the clock"
         << " is read by a statement or expression?" << endl;
    des->errors += 1;
    return false;
  }

  if (debug_synth2) {
    cerr << get_fileline() << ": NetEvWait::synth_sync: "
         << "Found and synthesized the FF clock." << endl;
  }

  connect(ff_clk->pin(0), pclk->pin(0));
  if (pclk->edge() == NetEvProbe::NEGEDGE) {
    ff_negedge = true;

    if (debug_synth2) {
      cerr << get_fileline() << ": debug: "
           << "Detected a NEGEDGE clock for the synthesized ff." << endl;
    }
  }

  /* Synthesize the input to the DFF. */
  return statement_->synth_sync(des, scope, ff_negedge, ff_clk, ff_ce, ff_aclr,
                                ff_aset, ff_aset_value, nex_map, nex_out,
                                bitmasks, events);
}

/*
 * This method is called for a process that is determined to be
 * synchronous. Create a NetFF device to hold the output from the
 * statement, and synthesize that statement in place.
 */
bool NetProcTop::synth_sync(Design* des) {
  if (debug_synth2) {
    cerr << get_fileline() << ": NetProcTop::synth_sync: "
         << "Process is apparently synchronous. Making NetFFs." << endl;
  }

  NexusSet nex_set;
  statement_->nex_output(nex_set);
  vector<verinum> aset_value(nex_set.size());

  /* Make a model FF that will connect to the first item in the
     set, and will also take the initial connection of clocks
     and resets. */

  // Create a net to carry the clock for the synthesized FFs.
  NetNet* clock = new NetNet(scope(), scope()->local_symbol(), NetNet::TRI,
                             &netvector_t::scalar_logic);
  clock->local_flag(true);
  clock->set_line(*this);

  NetBus ce(scope(), nex_set.size());
  NetBus nex_d(scope(), nex_set.size());
  NetBus nex_q(scope(), nex_set.size());
  NetBus aclr(scope(), nex_set.size());
  NetBus aset(scope(), nex_set.size());
  vector<NetProc::mask_t> bitmasks(nex_set.size());

  // Save links to the initial nex_d. These will be used later
  // to detect floating part-substitute and mux inputs that need
  // to be tied off.
  NetBus nex_in(scope(), nex_d.pin_count());
  for (unsigned idx = 0; idx < nex_in.pin_count(); idx += 1)
    connect(nex_in.pin(idx), nex_d.pin(idx));

  // The Q of the NetFF devices is connected to the output that
  // we are. The nex_q is a bundle of the outputs.
  for (unsigned idx = 0; idx < nex_q.pin_count(); idx += 1)
    connect(nex_q.pin(idx), nex_set[idx].lnk);

  // Connect the D of the NetFF devices later.

  /* Synthesize the input to the DFF. */
  bool negedge = false;
  bool flag = statement_->synth_sync(des, scope(), negedge, clock, ce, aclr,
                                     aset, aset_value, nex_set, nex_d, bitmasks,
                                     vector<NetEvProbe*>());
  if (!flag) {
    delete clock;
    return false;
  }

  flag = tie_off_floating_inputs_(des, nex_set, nex_in, bitmasks, true);
  if (!flag) return false;

  for (unsigned idx = 0; idx < nex_set.size(); idx += 1) {
    // ivl_assert(*this, nex_set[idx].nex);
    if (debug_synth2) {
      cerr << get_fileline() << ": debug: "
           << "Top level making a " << nex_set[idx].wid << "-wide "
           << "NetFF device." << endl;
    }

    NetFF* ff2 =
        new NetFF(scope(), scope()->local_symbol(), negedge, nex_set[idx].wid);
    des->add_node(ff2);
    ff2->set_line(*this);
    ff2->aset_value(aset_value[idx]);

    NetNet* tmp = nex_d.pin(idx).nexus()->pick_any_net();
    tmp->set_line(*this);
    assert(tmp);

    tmp = crop_to_width(des, tmp, ff2->width());

    connect(nex_q.pin(idx), ff2->pin_Q());
    connect(tmp->pin(0), ff2->pin_Data());

    connect(clock->pin(0), ff2->pin_Clock());
    if (ce.pin(idx).is_linked()) connect(ce.pin(idx), ff2->pin_Enable());
    if (aclr.pin(idx).is_linked()) connect(aclr.pin(idx), ff2->pin_Aclr());
    if (aset.pin(idx).is_linked()) connect(aset.pin(idx), ff2->pin_Aset());
#if 0
	    if (ff->pin_Sset().is_linked())
		  connect(ff->pin_Sset(), ff2->pin_Sset());
	    if (ff->pin_Sclr().is_linked())
		  connect(ff->pin_Sclr(), ff2->pin_Sclr());
#endif
  }

  // The "clock" net was just to carry the connection back
  // to the flip-flop. Delete it now. The connection will
  // persist.
  delete clock;

  synthesized_design_ = des;
  return true;
}

class synth2_f : public functor_t {
 public:
  void process(Design*, NetProcTop*);

 private:
};

/*
 * Look at a process. If it is asynchronous, then synthesize it as an
 * asynchronous process and delete the process itself for its gates.
 */
void synth2_f::process(Design* des, NetProcTop* top) {
  if (top->attribute(perm_string::literal("ivl_synthesis_off")).as_ulong() != 0)
    return;

  /* If the scope that contains this process as a cell attribute
     attached to it, then skip synthesis. */
  if (top->scope()
          ->attribute(perm_string::literal("ivl_synthesis_cell"))
          .len() > 0)
    return;

  /* Create shared pullup and pulldown nodes (if they don't already
     exist) for use when creating clock/gate enables. */
  top->scope()->add_tie_hi(des);
  top->scope()->add_tie_lo(des);

  if (top->is_synchronous()) {
    bool flag = top->synth_sync(des);
    if (!flag) {
      cerr << top->get_fileline() << ": error: "
           << "Unable to synthesize synchronous process." << endl;
      des->errors += 1;
      return;
    }
    des->delete_process(top);
    return;
  }

  if (!top->is_asynchronous()) {
    bool synth_error_flag = false;
    if (top->attribute(perm_string::literal("ivl_combinational")).as_ulong() !=
        0) {
      cerr << top->get_fileline() << ": error: "
           << "Process is marked combinational,"
           << " but isn't really." << endl;
      des->errors += 1;
      synth_error_flag = true;
    }

    if (top->attribute(perm_string::literal("ivl_synthesis_on")).as_ulong() !=
        0) {
      cerr << top->get_fileline() << ": error: "
           << "Process is marked for synthesis,"
           << " but I can't do it." << endl;
      des->errors += 1;
      synth_error_flag = true;
    }

    if (!synth_error_flag)
      cerr << top->get_fileline() << ": warning: "
           << "Process not synthesized." << endl;

    return;
  }

  if (!top->synth_async(des)) {
    cerr << top->get_fileline() << ": error: "
         << "Unable to synthesize asynchronous process." << endl;
    des->errors += 1;
    return;
  }

  des->delete_process(top);
}

void synth2(Design* des) {
  synth2_f synth_obj;
  des->functor(&synth_obj);
}
